package com.googlecode.jsqlconverter.frontend.cli;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.googlecode.jsqlconverter.definition.Statement;
import com.googlecode.jsqlconverter.filter.Filter;
import com.googlecode.jsqlconverter.parser.Parser;
import com.googlecode.jsqlconverter.parser.ParserException;
import com.googlecode.jsqlconverter.parser.callback.ParserCallback;
import com.googlecode.jsqlconverter.producer.Producer;
import com.googlecode.jsqlconverter.producer.ProducerException;
import com.googlecode.jsqlconverter.producer.interfaces.FinalInterface;
import com.googlecode.jsqlconverter.service.EntryPoint;
import com.googlecode.jsqlconverter.service.Parameter;
import com.googlecode.jsqlconverter.service.Service;
import com.googlecode.jsqlconverter.service.ServiceType;
import com.googlecode.jsqlconverter.service.ServiceUtil;

public class SQLConverterCLI implements ParserCallback {
	private static final String FROM = "--from";
	private static final String TO = "--to";
	private static final String FILTER = "--filter";
	private static final Map<ServiceType,String> prefixMap=new HashMap<ServiceType,String>() {{
		put(ServiceType.Filter,FILTER+"<n>");
		put(ServiceType.Parser,FROM);
		put(ServiceType.Producer,TO);
	}};
	private ServiceUtil su;
	private Parser parser;
	private Producer producer;
	private List<Filter> filter;

	public SQLConverterCLI(String[] args) throws Exception {
		su = new ServiceUtil();
		CLIParser cli = new CLIParser(args);

		if (cli.isEmpty()) {
			printHelp(su);
			System.exit(0);
		}

		String parserName = cli.getString(FROM);
		String producerName = cli.getString(TO);

		HashMap<String, Object> parserArgs = cli.getArgsWithPrefix(FROM + "-");
		HashMap<String, Object> producerArgs = cli.getArgsWithPrefix(TO + "-");
		filter=buildFilterList(cli);

		Service parserService = su.getFirstService(parserName, ServiceType.Parser);
		Service producerService = su.getFirstService(producerName, ServiceType.Producer);

		SimpleEntry<Parameter, Object>[] matchedParserArgs = parserService.getMatchingConstructorArguments(parserArgs);
		SimpleEntry<Parameter, Object>[] matchedProducerArgs = producerService.getMatchingConstructorArguments(producerArgs);

		parser = (Parser) parserService.newInstance(matchedParserArgs);
		producer = (Producer) producerService.newInstance(matchedProducerArgs);
	}

	private List<Filter> buildFilterList(CLIParser cli) throws  Exception {
		int i=1;
		List<Filter> ret=new ArrayList<Filter>();
		do {
			String nfilter=FILTER+String.valueOf(i);
			String filterName = cli.getString(nfilter);
			if (filterName==null) break; 
			HashMap<String, Object> filterArgs = cli.getArgsWithPrefix(nfilter + "-");
			Service filterService = su.getFirstService(filterName, ServiceType.Filter);
			SimpleEntry<Parameter, Object>[] matchedFilterArgs = filterService.getMatchingConstructorArguments(filterArgs);
			ret.add((Filter) filterService.newInstance(matchedFilterArgs));
			i++;
		} while (true);
		return ret;
	}

	public void run() throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ParserException, ProducerException {
		long beforeMili = System.currentTimeMillis();

		parser.parse(this);

		if (producer instanceof FinalInterface) {
			((FinalInterface)producer).doFinal();
		}

		long runTimeMili = System.currentTimeMillis() - beforeMili;

		log("Runtime: " + runTimeMili + "ms");
	}

	private void printHelp(ServiceUtil su) {
		System.out.println("Parsers");

		for (Service s : su.getServices()) {
			if (s.getServiceType()==ServiceType.Parser) {
				for (EntryPoint ep : s.getEntryPoints()) {
					System.out.print("\t" + FROM + " " + s.getFlattenedName());

					for (Parameter p : ep.getParameters()) {
						printParameter(p, s.getServiceType());
					}

					System.out.println();
				}
			}
		}

		System.out.println();
		System.out.println("Filters");

		for (Service s : su.getServices()) {
			if (s.getServiceType()==ServiceType.Filter) {
				for (EntryPoint ep : s.getEntryPoints()) {
					System.out.print("\t" + FILTER + "<n> " + s.getFlattenedName());

					for (Parameter p : ep.getParameters()) {
						printParameter(p, s.getServiceType());
					}

					System.out.println();
				}
			}
		}

		System.out.println();

		System.out.println("Producers");

		for (Service s : su.getServices()) {
			if (s.getServiceType()==ServiceType.Producer) {
				for (EntryPoint ep : s.getEntryPoints()) {
					System.out.print("\t" + TO + " " + s.getFlattenedName());

					for (Parameter p : ep.getParameters()) {
						// If the output is a PrintStream, hide it as we always use System.out
						if (p.getClassType() != PrintStream.class) {
							printParameter(p, s.getServiceType());
						}
					}

					System.out.println();
				}
			}
		}
	}

	private void printParameter(Parameter p, ServiceType type) {
		System.out.print(" ");

		if (p.isOptional()) System.out.print("[");

		Class<?> inputClass = p.getClassType();
		String inputName = "";

		if (InputStream.class.isAssignableFrom(inputClass)) {
			inputName = "file";
		} else if (File.class.isAssignableFrom(inputClass)) {
			inputName = "file";
		} else if (Integer.class.isAssignableFrom(inputClass)) {
			inputName = "number";
		} else if (Boolean.class.isAssignableFrom(inputClass)) {
			inputName = "true/false";
		} else if (Character.class.isAssignableFrom(inputClass)) {
			inputName = "char";
		} else if (String.class.isAssignableFrom(inputClass)) {
			inputName = "text";
		} else if (Enum.class.isAssignableFrom(inputClass)) {
			Enum<?>[] enumList = (Enum<?>[]) p.getClassType().getEnumConstants();
			inputName = "";

			for (int i=0; i<enumList.length; i++) {
				if (i > 0) {
					inputName += " | ";
				}

				inputName += enumList[i];
			}
		}

		if (p.isOptional() && p.getDefaultValue() != null) {
			inputName += " = " + p.getDefaultValue();
		}

		System.out.print(prefixMap.get(type)+ "-" + p.getFlattenedName() + " <" + inputName + ">");

		if (p.isOptional()) System.out.print("]");
	}

	@Override
	public void produceStatement(Statement statement) {
		try {
			for (Filter f : filter) {
				statement=f.filter(statement);
				// statement could have been removed by the filter
				if (statement==null) return;
			}
			producer.produce(statement);
		} catch (ProducerException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void log(String message) {
		System.err.println(message);
	}

	public static void main(String args[]) throws Exception {
		new SQLConverterCLI(args).run();
	}
}
